/* * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.xwiki.contrib.mail.internal; import java.io.ByteArrayOutputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Properties; import javax.inject.Inject; import javax.mail.AuthenticationFailedException; import javax.mail.Flags; import javax.mail.Folder; import javax.mail.FolderNotFoundException; import javax.mail.Message; import javax.mail.MessagingException; import javax.mail.Session; import javax.mail.Store; import javax.mail.internet.MimeMessage; import javax.mail.search.FlagTerm; import javax.mail.search.MessageIDTerm; import javax.mail.util.SharedByteArrayInputStream; import org.apache.commons.lang.ArrayUtils; import org.slf4j.Logger; import org.xwiki.component.annotation.Component; import org.xwiki.component.manager.ComponentManager; import org.xwiki.contrib.mail.SourceConnectionErrors; import org.xwiki.contrib.mail.internal.source.ServerAccountSource; /** * @version $Id$ */ @Component public class DefaultMailReader extends AbstractMailReader { // TODO manage topics max length for compatibility private static final int MAIL_HEADER_MAX_LENGTH = 255; private Session session; private Store store; @Inject private Logger logger; @Inject private ComponentManager componentManager; public void setMailSource(final ServerAccountSource mailSource) { super.setMailSource(mailSource); } public ServerAccountSource getMailSource() { return (ServerAccountSource) super.getMailSource(); } /** * {@inheritDoc} * * @throws MessagingException * @see org.xwiki.contrib.mail.IMailReader#fetch(java.lang.String, int, java.lang.String, java.lang.String, * java.lang.String, java.lang.String, boolean) */ @Override public List<Message> read(final String folder, final boolean onlyUnread) throws MessagingException { return read(folder, onlyUnread, -1); } /** * {@inheritDoc} * * @throws MessagingException * @throws * @throws Exception * @see org.xwiki.contrib.mail.IMailReader#read(java.lang.String, int, java.lang.String, java.lang.String, * java.lang.String, java.lang.String, boolean, int) */ @Override public List<Message> read(final String folder, final boolean onlyUnread, final int max) throws MessagingException { assert (getMailSource() != null); assert (getMailSource().getHostname() != null); store = null; List<Message> messages = new ArrayList<Message>(); boolean isGmail = getMailSource().getHostname() != null && getMailSource().getHostname().endsWith(".gmail.com"); logger.info("Trying to retrieve mails from server " + getMailSource().getHostname()); this.session = createSession(getMailSource().getProtocol(), getMailSource().getAdditionalProperties(), isGmail, getMailSource().isAutoTrustSSLCertificates()); // Get a Store object store = session.getStore(); // Connect to the mail account store.connect(getMailSource().getHostname(), getMailSource().getPort(), getMailSource().getUsername(), getMailSource().getPassword()); Folder fldr; // Specifically for GMAIL ... if (isGmail) { fldr = store.getDefaultFolder(); } fldr = store.getFolder(folder); fldr.open(Folder.READ_WRITE); Message[] msgsArray; // Searches for mails not already read if (onlyUnread) { FlagTerm searchterms = new FlagTerm(new Flags(Flags.Flag.SEEN), false); msgsArray = fldr.search(searchterms); } else { msgsArray = fldr.getMessages(); } if (max > 0 && msgsArray.length > max) { msgsArray = (Message[]) ArrayUtils.subarray(msgsArray, 0, max); } messages = new ArrayList<Message>(Arrays.asList(msgsArray)); logger.info("Found " + messages.size() + " messages"); // Note: we leave the Store opened to allow reading returned Messages return messages; } /** * {@inheritDoc} * * @throws MessagingException * @throws * @throws Exception * @see org.xwiki.contrib.mail.IMailReader#read(java.lang.String, int, java.lang.String, java.lang.String, * java.lang.String, java.lang.String, boolean, int) */ @Override public Message read(final String folder, final String messageid) throws MessagingException { assert (getMailSource() != null); assert (getMailSource().getHostname() != null); Message message = null; store = null; boolean isGmail = getMailSource().getHostname() != null && getMailSource().getHostname().endsWith(".gmail.com"); logger.info("Trying to retrieve mails from server " + getMailSource().getHostname()); this.session = createSession(getMailSource().getProtocol(), getMailSource().getAdditionalProperties(), isGmail, getMailSource().isAutoTrustSSLCertificates()); // Get a Store object store = session.getStore(); // Connect to the mail account store.connect(getMailSource().getHostname(), getMailSource().getPort(), getMailSource().getUsername(), getMailSource().getPassword()); Folder fldr; // Specifically for GMAIL ... if (isGmail) { fldr = store.getDefaultFolder(); } fldr = store.getFolder(folder); fldr.open(Folder.READ_WRITE); // Search with message id Message[] messages = fldr.search(new MessageIDTerm(messageid)); if (messages.length > 0) { message = messages[0]; } logger.info("Found message " + message); return message; } @Override public ArrayList<FolderItem> getFolderTree() throws MessagingException { assert (getMailSource() != null); assert (getMailSource().getHostname() != null); ArrayList<FolderItem> folderItems = new ArrayList<FolderItem>(); store = null; boolean isGmail = getMailSource().getHostname() != null && getMailSource().getHostname().endsWith(".gmail.com"); logger.info("Listing folders for " + getMailSource().getHostname()); this.session = createSession(getMailSource().getProtocol(), getMailSource().getAdditionalProperties(), isGmail, getMailSource().isAutoTrustSSLCertificates()); // Get a Store object store = session.getStore(); // Connect to the mail account store.connect(getMailSource().getHostname(), getMailSource().getPort(), getMailSource().getUsername(), getMailSource().getPassword()); Folder defaultFolder = store.getDefaultFolder(); FolderItem item = new FolderItem(); item.setIndex(0); item.setLevel(0); item.setName(defaultFolder.getName()); item.setFullName(defaultFolder.getFullName()); if ((defaultFolder.getType() & javax.mail.Folder.HOLDS_MESSAGES) != 0) { item.setMessageCount(defaultFolder.getMessageCount()); item.setUnreadMessageCount(defaultFolder.getUnreadMessageCount()); item.setNewMessageCount(defaultFolder.getNewMessageCount()); } Folder[] folders = defaultFolder.list("*"); int index = 1; int level = 1; // TODO not really managing folders here, just listing them for (Folder folder : folders) { item = new FolderItem(); item.setIndex(index); item.setLevel(level); item.setName(folder.getName()); item.setFullName(folder.getFullName()); if ((folder.getType() & javax.mail.Folder.HOLDS_MESSAGES) != 0) { item.setMessageCount(folder.getMessageCount()); item.setUnreadMessageCount(folder.getUnreadMessageCount()); item.setNewMessageCount(folder.getNewMessageCount()); folderItems.add(item); } } store.close(); return folderItems; } /** * {@inheritDoc} * * @see org.xwiki.contrib.mail.IMailReader#check(java.lang.String, int, java.lang.String, java.lang.String, * java.lang.String, java.lang.String, boolean) */ @Override public int check(final String folder, final boolean onlyUnread) { int result; boolean toClose = true; // If store is currently connected, we don't need to close it after the check. // If it is not, we close it after to leave everything as it was. if (store != null && store.isConnected()) { toClose = false; } try { List<Message> messages = read(folder, onlyUnread); result = messages.size(); // FIXME: instead of converting exceptions to int code, would be better to create new Exception class // (like MailException) with provided error code, message and inner stacktrace, and present that to UI. } catch (AuthenticationFailedException e) { logger.warn("checkMails : ", e); return SourceConnectionErrors.AUTHENTICATION_FAILED.getCode(); } catch (FolderNotFoundException e) { logger.warn("checkMails : ", e); return SourceConnectionErrors.FOLDER_NOT_FOUND.getCode(); } catch (MessagingException e) { logger.warn("checkMails : ", e); if (e.getCause() instanceof java.net.UnknownHostException) { return SourceConnectionErrors.UNKNOWN_HOST.getCode(); } else { return SourceConnectionErrors.CONNECTION_ERROR.getCode(); } } catch (IllegalStateException e) { return SourceConnectionErrors.ILLEGAL_STATE.getCode(); } catch (Throwable t) { logger.warn("checkMails : ", t); return SourceConnectionErrors.UNEXPECTED_EXCEPTION.getCode(); } if (toClose) { close(); } logger.debug("checkMails : " + result + " available from " + getMailSource().getHostname()); return result; } private Session createSession(final String protocol, final Properties additionalProperties, final boolean isGmail, final boolean autoTrustSsl) { // Get a session. Use a blank Properties object. Properties props = new Properties(additionalProperties); // necessary to work with Gmail if (isGmail && !props.containsKey("mail.imap.partialfetch") && !props.containsKey("mail.imaps.partialfetch")) { props.put("mail.imap.partialfetch", "false"); props.put("mail.imaps.partialfetch", "false"); } props.put("mail.store.protocol", protocol); // TODO set this as an option (auto-trust certificates for SSL) props.put("mail.imap.ssl.checkserveridentity", "false"); props.put("mail.imaps.ssl.trust", "*"); /* * MailSSLSocketFactory socketFactory = new MailSSLSocketFactory(); socketFactory.setTrustAllHosts(true); * props.put("mail.imaps.ssl.socketFactory", socketFactory); */ Session session = Session.getInstance(props, null); session.setDebug(true); return session; } public Session getSession() { return this.session; } @Override public void close() { if (store != null && store.isConnected()) { try { store.close(); } catch (MessagingException e) { logger.debug("Could not close connection."); } } } /** * {@inheritDoc} * * @see org.xwiki.contrib.mail.IMailReader#cloneEmail(javax.mail.Message, java.lang.String, java.lang.String) */ public MimeMessage cloneEmail(final Message mail) { MimeMessage cmail = null; try { ByteArrayOutputStream bos = new ByteArrayOutputStream(); mail.writeTo(bos); bos.close(); SharedByteArrayInputStream bis = new SharedByteArrayInputStream(bos.toByteArray()); cmail = new MimeMessage(this.session, bis); bis.close(); } catch (Exception e) { logger.warn("Could not clone email", e); return null; } return cmail; } }